<?php
/**
 * lsys orm
 * @author     Lonely <shan.liu@msn.com>
 * @copyright  (c) 2017 Lonely <shan.liu@msn.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0
 */

namespace LSYS\ORM;
use LSYS\ORM;
abstract class Entity implements \serializable,\IteratorAggregate{
	/**
	 * get iterator
	 * use : iterator_to_array($entity);
	 * @return \ArrayIterator
	 */
	public function getIterator () {
		return new \ArrayIterator($this->as_array());
	}
	/**
	 * @var ORM
	 */
	protected $_orm;
	protected static $_orm_cache=NULL;
	protected static $_orm_ref=0;
	/**
	 * @param ORM $orm
	 */
	public function __construct(ORM $orm=null) {
		if ($orm===null)$orm=$this->_init_orm();
		else if (static::$_orm_cache===null)static::$_orm_cache=$orm;
		static::$_orm_ref++;
		$this->_orm = $orm;
	}
	public function __destruct(){
		static::$_orm_ref--;
		if (static::$_orm_ref==0)static::$_orm_cache=null;
	}
	/**
	 * @return array
	 */
	public function as_array() {
		$object = array ();
		$def=null;
		foreach ( $this->_orm->table_columns() as $key ) {
			if(isset($this->{$key}))
				$object [$key] = $this->{$key};
			else{
				if ($def==null)$def=get_class_vars(get_called_class());
				$object [$key] = array_key_exists($key, $def)?$def[$key]:NULL;
			}
		}
		return $object;
	}
	/**
	 * Set values from an array with support for one-one relationships.
	 * This method should be used
	 * for loading in post data, etc.
	 *
	 * @param array $values
	 *        	Array of column => val
	 * @param array $expected
	 *        	Array of keys to take from $values
	 * @return $this
	 */
	public function values(array $values, array $expected = NULL) {
		// Default to expecting everything except the primary key
		if ($expected === NULL) {
			$expected = $this->_orm->table_columns();
			unset ( $values [$this->_orm->primary_key()] );
		}
		foreach ( $expected as $column ) {
			if (! array_key_exists ( $column, $values )) continue;
			$this->$column = $values [$column];
		}
		return $this;
	}
	/**
	 * Returns the value of the primary
	 *
	 * @return mixed Primary
	 */
	public function pk()
	{
		$primary_key=$this->_orm->primary_key();
		if (!isset($this->$primary_key)) return null;
		return $this->{$primary_key};
	}
	/**
	 * Allows serialization of only the object data and state, to prevent
	 * "stale" objects being unserialized, which also requires less memory.
	 *
	 * @return string
	 */
	public function serialize() {
		$data=array();
		$def=NULL;
		foreach ( $this->_orm->table_columns() as $key) {
			if(isset($this->{$key})) $data [$key] = $this->{$key};
			else{
				if ($def==null)$def=get_class_vars(get_called_class());
				$data [$key] = array_key_exists($key, $def)?$def[$key]:NULL;
			}
		}
		return serialize($data);
	}
	/**
	 * Prepares the database connection and reloads the object.
	 *
	 * @param string $data
	 *        	String for unserialization
	 * @return void
	 */
	public function unserialize($data) {
		$data=unserialize($data);
		if(is_array($data)){
			foreach ($data as $name => $var)
			{
				if(!property_exists($this,$name))continue;
				$this->{$name} = $var;
			}
		}else{
			$this->clear();
		}
		$this->_orm=$this->_init_orm();
		static::$_orm_ref++;
	}
	/**
	 * @return boolean
	 */
	public function loaded() {
		return !in_array($this->pk(),array(null,false));
	}
	/**
	 * clear entity
	 * @return $this
	 */
	public function clear(){
		$def=get_class_vars(get_called_class());
		foreach ($this->_orm->table_columns() as $k){
			if (array_key_exists($k, $def)&&isset($this->{$k})) $this->{$k}=$def[$k];
		}
		$this->{$this->_orm->primary_key()}=NULL;
		$this->_orm->reset();
		return $this;
	}
	/**
	 * diff Entity
	 * @param Entity $entity
	 * @param array $field
	 * @return array
	 */
	public function diff(Entity $entity,$field=null){
		if(is_string($field)) $field=array($field);
		$columns=$this->_orm->table_columns();
		if (is_array($field)) $columns= array_intersect($columns, $field);
		$diff=array();
		foreach ($columns as $v){
			if ($entity->{$v}!==$this->{$v}){
				$diff[]=$v;
			}
		}
		return $diff;
	}
	/**
	 * entity to string
	 * @return int|NULL
	 */
	public function __toString(){
		return $this->pk();
	}
	/**
	 * interior orm obj
	 * @throws Exception
	 */
	protected function _init_orm(){
		if (static::$_orm_cache===NULL){
			$cls=get_called_class();
			$orm =substr_replace($cls,"ORM",strrpos($cls,"\\")+1,6);
			if (!class_exists($orm)) throw new Exception(__("can't find :orm orm class",array("orm"=>$orm)));
			static::$_orm_cache=new $orm;
		}
		return static::$_orm_cache;
	}
	/**
	 * @return \LSYS\ORM
	 */
	public function get_orm(){
		$this->_orm->entity($this);
		return $this->_orm;
	}
	/**
	 * dump object
	 */
	public function __debugInfo(){
		$out=get_object_vars($this);
		unset($out['_orm']);
		return $out;
	}
}